#pragma once

#include "pch.h"
#include "Method.h"

namespace mono
{
    struct Object
    {
        MonoObject *m_MonoObject;

        Object(MonoObject *o, bool runtimeInit=true) : m_MonoObject(o)
        {
            if (runtimeInit)
            {

                mono_runtime_object_init(m_MonoObject);
            }
        };

        template <typename ResultType>
        ResultType InvokeRes(const Method& method, void *args[]);

        void PushArgs(std::vector<void *> &addresses)
        {
        }
        template <typename First, typename... Args>
        void PushArgs(std::vector<void *> &addresses, First &first, Args &&...args)
        {
            PushArg<First>(addresses, first);
            PushArgs(addresses, std::forward<Args>(args)...);
        };

        template <typename ArgType>
        void PushArg(std::vector<void *> &addresses, ArgType &argType);

        template <typename ReturnType, typename... Args>
        ReturnType InvokeMethodArgs(const Method& method, Args &&...args)
        {
            std::vector<void *> voidArgs;
            PushArgs(voidArgs, std::forward<Args>(args)...);
            Object obj(InvokeMonoRes(method, voidArgs.data()), false);

            return obj.As<ReturnType>();
       
        };

        template <typename... Args>
        void InvokeVoidArgs(const Method &method, Args &&...args)
        {
            std::vector<void *> voidArgs;
            PushArgs(voidArgs, std::forward<Args>(args)...);
            InvokeMonoRes(method, voidArgs.data());
        };

        template <typename ArgType>
        ArgType As();

        template<typename ArgType>
        void Load(const ArgType& src);
        
        MonoObject *InvokeMonoRes(const Method& method, void *args[])
        {
            return mono_runtime_invoke(method.m_MonoMethod, m_MonoObject, args, 0);
        };

        void InvokeVoid(const Method& method, void *args[])
        {
            mono_runtime_invoke(method.m_MonoMethod, m_MonoObject, args, 0);
        }


    };
}
